import { FormsModule } from '@angular/forms';
import { ChangeDetectionStrategy, Component, ViewChild } from '@angular/core';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
import { OverlayService } from '../core/api';
import { DatePickerModule } from './date-picker.module';
import { LvDateTimeOption } from './interface';

@Component({
  template: `
    <lv-date-picker
      [(ngModel)]="date"
      [lvSize]="lvSize"
      [lvFormat]="lvFormat"
      [lvInline]="lvInline"
      [lvReadonly]="lvReadonly"
      [lvDisabled]="lvDisabled"
      [lvShowClear]="lvShowClear"
      [lvPanelClass]="lvPanelClass"
      [lvShowTime]="lvShowTime"
      [lvNowAsBaseTime]="lvNowAsBaseTime"
      [lvDisabledDate]="lvDisabledDate"
      [lvOnlyShowActiveCell]="lvOnlyShowActiveCell"
      [lvPanelTitleReadonly]="lvPanelTitleReadonly"
      [lvCalendarMonthOperate]="lvCalendarMonthOperate"
      [lvCalendarYearOperate]="lvCalendarYearOperate"
      [lvTimezoneOffset]="lvTimezoneOffset"
      [lvShowTodayButton]="lvShowTodayButton"
      [lvShowNowButton]="lvShowNowButton"
      [lvFooterExtra]="lvFooterExtra"
      [lvPickerMode]="lvPickerMode"
      (ngModelChange)="modelChange()"
      (lvOpenChange)="openChange()"
      (lvActiveDateChange)="activeDateChange()"
    ></lv-date-picker>

    <ng-template #footerTpl>
      <div class="lv-date-picker-cell" style="padding: 8px 24px; background: #fafafa; width: 100%">Here is the expanded content!</div>
    </ng-template>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
class TestDatePickerComponent {
  @ViewChild('footerTpl') footerTpl;

  date = new Date('2023-07-30');
  lvPickerMode = 'date';
  lvSize: 'default' | 'large' = 'default';
  lvFormat = 'yyyy-MM-dd HH:mm:ss';
  lvInline = false;
  lvReadonly = false;
  lvDisabled = false;
  lvShowClear = true;
  lvPanelClass = '';
  lvShowTime: boolean | LvDateTimeOption = false;
  lvNowAsBaseTime = true;
  lvDisabledDate: (date: Date) => boolean;
  lvOnlyShowActiveCell = false;
  lvPanelTitleReadonly = false;
  lvCalendarMonthOperate = { show: true, disableChange: false };
  lvCalendarYearOperate = { show: true, disableChange: false };
  lvTimezoneOffset;
  lvShowTodayButton = true;
  lvShowNowButton = true;
  lvFooterExtra;

  openChange() {}

  activeDateChange() {}

  modelChange() {}
}

describe('DatePickerComponent', () => {
  beforeEach(waitForAsync(() => {
    TestBed.configureTestingModule({
      declarations: [TestDatePickerComponent],
      imports: [DatePickerModule, FormsModule, NoopAnimationsModule],
      teardown: { destroyAfterEach: true },
      providers: [OverlayService],
    }).compileComponents();
  }));

  describe('render', () => {
    let component: TestDatePickerComponent;
    let fixture: ComponentFixture<TestDatePickerComponent>;
    const panelTagName = 'lv-date-picker-panel';

    const delay = () => {
      tick(500);
      fixture.detectChanges();
    };

    const renderComponent = (param, genSnapshot = true) => {
      for (const [key, value] of Object.entries(param)) {
        component[key] = value;
      }
      delay();
      delay();
      if (genSnapshot) {
        expect(fixture).toMatchSnapshot();
      }
    };

    const getPanel = (inline = false) => {
      const input = fixture.nativeElement.querySelector('.lv-date-picker-main .lv-input');
      input.dispatchEvent(new Event('click'));
      delay();
      delay();
      if (!inline) {
        return document.querySelector(panelTagName);
      }
      return document.querySelector(panelTagName);
    };

    beforeEach(() => {
      fixture = TestBed.createComponent(TestDatePickerComponent);
      component = fixture.componentInstance;
    });

    it('with basic', fakeAsync(() => {
      renderComponent({});
      expect(getPanel()).toMatchSnapshot();
    }));

    it('with month Mode', fakeAsync(() => {
      renderComponent({ lvPickerMode: 'month' });
      expect(getPanel()).toMatchSnapshot();
    }));

    it('with year Mode', fakeAsync(() => {
      renderComponent({ lvPickerMode: 'year' });
      expect(getPanel()).toMatchSnapshot();
    }));

    it('with simple Mode', fakeAsync(() => {
      renderComponent({ lvPickerMode: 'simple' });
      expect(getPanel()).toMatchSnapshot();
    }));

    it('with show time', fakeAsync(() => {
      renderComponent({ lvShowTime: true });
      expect(getPanel()).toMatchSnapshot();
    }));

    it('with large size', fakeAsync(() => {
      renderComponent({ lvSize: 'large' });
    }));

    it('with disabled', fakeAsync(() => {
      renderComponent({ lvDisabled: true });
    }));

    it('with disabled date', fakeAsync(() => {
      renderComponent(
        {
          date: new Date('2023-07-30 08:59:59'),
          lvDisabledDate: date => {
            if (date > component.date) {
              return true;
            }
          },
          lvShowTime: {
            format: 'HH:mm:ss',
            disabledHour: hour => {
              if (hour < 5 || hour > 18) {
                return true;
              }
            },
            disabledMinute: (hour, minute) => {
              if (hour === 8 && minute < 30) {
                return true;
              }
            },
            disabledSecond: (hour, minute, second) => {
              if (minute < 30 && second > 30) {
                return true;
              }
            },
          },
        },
        false
      );
      expect(getPanel()).toMatchSnapshot();
    }));

    it('with extra footer', fakeAsync(() => {
      renderComponent({ lvFooterExtra: component.footerTpl }, false);
      expect(getPanel()).toMatchSnapshot();
    }));
  });

  describe('work', () => {
    let component: TestDatePickerComponent;
    let fixture: ComponentFixture<TestDatePickerComponent>;
    const panelTagName = 'lv-date-picker-panel';
    const inputSelector = '.lv-date-picker-main .lv-input';

    const delay = () => {
      tick(500);
      fixture.detectChanges();
    };

    const renderComponent = param => {
      for (const [key, value] of Object.entries(param)) {
        component[key] = value;
      }
      delay();
      delay();
    };

    const getInput = () => fixture.nativeElement.querySelector(inputSelector);

    const openPanel = () => {
      getInput().dispatchEvent(new Event('click'));
      delay();
      delay();
    };

    const getPanel = () => document.querySelector(panelTagName);

    const getDay = (day: string, days?: Element[]) => {
      if (!days) {
        days = Array.from(getPanel().querySelectorAll('.lv-date-picker-cell'));
      }
      return days.find(item => {
        if (!item.parentElement.className.includes('lv-date-picker-last-month-day')) {
          return item.textContent.trim() === day;
        } else {
          return false;
        }
      });
    };

    // 获取头部年、月dom
    const getYearText = () => getPanel().querySelectorAll('.center span')[0];
    const getMonthText = () => getPanel().querySelectorAll('.center span')[1];

    beforeEach(() => {
      fixture = TestBed.createComponent(TestDatePickerComponent);
      component = fixture.componentInstance;
    });

    it('with modelChange', fakeAsync(() => {
      jest.spyOn(component, 'modelChange');
      renderComponent({});
      openPanel();
      const day20 = getDay('20');
      day20.dispatchEvent(new Event('click'));
      delay();
      delay();
      expect(component.modelChange).toBeCalled();
      expect(getInput().value).toContain('2023-07-20');
    }));

    it('with click header', fakeAsync(() => {
      jest.spyOn(component, 'activeDateChange');
      renderComponent({});
      openPanel();

      // 切换月视图
      expect(getMonthText().textContent).toContain('7月');
      getMonthText().dispatchEvent(new Event('click'));
      delay();
      const months = Array.from(document.querySelectorAll('.lv-date-picker-month-cell'));
      const May = months[4];
      expect(May.textContent).toContain('5月');
      May.dispatchEvent(new Event('click'));
      delay();
      expect(component.activeDateChange).toHaveBeenCalledTimes(1);
      expect(getMonthText().textContent).toContain('5月');

      // 切换年视图`
      expect(getYearText().textContent).toContain('2023年');
      getYearText().dispatchEvent(new Event('click'));
      delay();
      expect(getYearText().textContent).toContain('2020 - 2029');

      // 切换10年视图
      getYearText().dispatchEvent(new Event('click'));
      delay();
      expect(getYearText().textContent).toContain('2000 - 2099');
      const tenYears = Array.from(document.querySelectorAll('.lv-date-picker-10year-cell'));
      tenYears[1].dispatchEvent(new Event('click'));
      delay();
      expect(component.activeDateChange).toHaveBeenCalledTimes(2);
      expect(getYearText().textContent).toContain('2000 - 2009');

      const years = Array.from(document.querySelectorAll('.lv-date-picker-year-cell'));
      years[1].dispatchEvent(new Event('click'));
      delay();
      expect(component.activeDateChange).toHaveBeenCalledTimes(3);
      expect(getYearText().textContent).toContain('2000年');

      const day31 = getDay('31');
      day31.dispatchEvent(new Event('click'));
      delay();
      delay();

      expect(getInput().value).toContain('2000-05-31');
    }));

    it('with title readonly', fakeAsync(() => {
      renderComponent({ lvPanelTitleReadonly: true });
      openPanel();
      expect(getMonthText().className).toContain('readonly');
      expect(getYearText().className).toContain('readonly');
    }));

    it('with year display', fakeAsync(() => {
      renderComponent({ lvCalendarYearOperate: { show: false } });
      openPanel();
      expect(getYearText().textContent).not.toContain('2023年');
    }));

    it('with year disabled', fakeAsync(() => {
      renderComponent({ lvCalendarYearOperate: { show: true, disableChange: true } });
      openPanel();
      expect(getYearText().textContent).toContain('2023年');
      expect(getYearText().className).toContain('readonly');
    }));

    it('with month display', fakeAsync(() => {
      renderComponent({ lvCalendarMonthOperate: { show: false } });
      openPanel();
      expect(getMonthText()).toBeFalsy();
    }));

    it('with month disabled', fakeAsync(() => {
      renderComponent({ lvCalendarMonthOperate: { show: true, disableChange: true } });
      openPanel();
      expect(getMonthText().textContent).toContain('7月');
      expect(getMonthText().className).toContain('readonly');
    }));

    it('with show time', fakeAsync(() => {
      jest.spyOn(component, 'modelChange');
      renderComponent({ date: new Date('2023-07-30 10:00:00'), lvShowTime: true });
      delay();
      delay();
      openPanel();
      const day31 = getDay('31');
      day31.dispatchEvent(new Event('click'));
      delay();
      delay();
      delay();
      expect(getInput().value).toContain('2023-07-31 10:00:00');
    }));

    it('with disabled', fakeAsync(() => {
      renderComponent({ lvDisabled: true });
      openPanel();
      expect(getPanel()).toBeNull();
    }));

    // TODO: 头部icon检测

    it('with visible change', fakeAsync(() => {
      jest.spyOn(component, 'openChange');
      fixture.detectChanges();
      openPanel();
      expect(component.openChange).toBeCalledTimes(1);
      openPanel();
      delay();
      expect(component.openChange).toBeCalledTimes(2);
      expect(getPanel()).toBeNull();
    }));

    it('with clear', fakeAsync(() => {
      fixture.detectChanges();
      delay();
      delay();
      expect(getInput().value).toContain('2023-07-30');
      const clearIcon = fixture.nativeElement.querySelector('.lv-date-picker-clear');
      clearIcon.dispatchEvent(new Event('click'));
      delay();
      delay();
      expect(getInput().value).toBeFalsy();
    }));
  });
});
